/**
 * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 2.1 of the License, or (at your option)
 * any later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 */

package com.mad.bikepony.service.impl;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;

import com.liferay.portal.kernel.exception.SystemException;
import com.liferay.portal.kernel.json.JSONArray;
import com.liferay.portal.kernel.json.JSONException;
import com.liferay.portal.kernel.json.JSONFactoryUtil;
import com.liferay.portal.kernel.json.JSONObject;
import com.liferay.portal.kernel.util.HttpUtil;
import com.liferay.portal.kernel.util.Validator;
import com.mad.bikepony.model.Agent;
import com.mad.bikepony.model.AgentState;
import com.mad.bikepony.model.Delivery;
import com.mad.bikepony.model.impl.DeliveryImpl;
import com.mad.bikepony.service.base.DeliveryLocalServiceBaseImpl;

/**
 * The implementation of the delivery local service.
 *
 * <p>
 * All custom service methods should be put in this class. Whenever methods are added, rerun ServiceBuilder to copy their definitions into the {@link com.mad.bikepony.service.DeliveryLocalService} interface.
 *
 * <p>
 * This is a local service. Methods of this service will not have security checks based on the propagated JAAS credentials because this service can only be accessed from within the same VM.
 * </p>
 *
 * @author nicola
 * @see com.mad.bikepony.service.base.DeliveryLocalServiceBaseImpl
 * @see com.mad.bikepony.service.DeliveryLocalServiceUtil
 */
public class DeliveryLocalServiceImpl extends DeliveryLocalServiceBaseImpl {
	/*
	 * NOTE FOR DEVELOPERS:
	 *
	 * Never reference this interface directly. Always use {@link com.mad.bikepony.service.DeliveryLocalServiceUtil} to access the delivery local service.
	 */
	
	public Delivery addNewParcel(String senderName,String senderSurname,String senderPickupAddress,String senderEmail,String senderAdditionalInfo,String recipientName,String recipientSurname,String recipientDeliveryAddress,String recipientEmail,String recipientAdditionalInfo) throws SystemException
	{
		if(!Validator.isAddress(senderEmail) || !Validator.isAddress(recipientEmail))
		{
			throw new SystemException();
		}
		//TODO check addresses using Google Geocode API
		if(!checkAddress(senderPickupAddress) || !checkAddress(recipientDeliveryAddress))
		{
			throw new SystemException();
		}
		if(Validator.isNull(senderName) || Validator.isNull(recipientName) || Validator.isNull(senderSurname) || Validator.isNull(recipientSurname))
		{
			throw new SystemException();
		}
		
		/*tracking code and delivery code generation*/
		
		long trackingCodeTimestamp = System.currentTimeMillis()+100;
		String trackingCode = Long.toString(trackingCodeTimestamp, 32);
		long deliveryCodeTimestamp=0;
		
		while((deliveryCodeTimestamp = System.currentTimeMillis()+200)==trackingCodeTimestamp)
		{
			deliveryCodeTimestamp = System.currentTimeMillis()+200;
		}
		String deliveryCode = Long.toString(deliveryCodeTimestamp,32);
		Delivery del = new DeliveryImpl();
		JSONObject joSender = JSONFactoryUtil.createJSONObject();
		joSender.put("name", senderName);
		joSender.put("surname", senderSurname);
		joSender.put("more", senderAdditionalInfo);
		del.setSenderAdditionalInfo(joSender.toString());
		JSONObject joRecipient = JSONFactoryUtil.createJSONObject();
		joRecipient.put("name", recipientName);
		joRecipient.put("surname", recipientSurname);
		joRecipient.put("more", recipientAdditionalInfo);
		del.setRecipientAdditionalInfo(joRecipient.toString());
		/*
		del.setRecipientAdditionalInfo("{'name':'"+recipientName+"','surname':'"+recipientSurname+"','more':' "+recipientAdditionalInfo==null?"":recipientAdditionalInfo+"'}");
		del.setSenderAdditionalInfo("{'name':'"+senderName+"','more':' "+senderAdditionalInfo==null?"":senderAdditionalInfo+"'}");
		*/
		del.setRecipientAddress(recipientDeliveryAddress);
		del.setRecipientEmail(recipientEmail);
		del.setSenderAddress(senderPickupAddress);
		del.setSenderEmail(senderEmail);
		del.setDeliveryCode(deliveryCode);
		del.setTrackingCode(trackingCode);
		
		/*
		 * TODO assign to an active agent
		 * */
			
				Long agentId=0l;
				Agent agent=null;
				//check from the active agents, and choose the nearest one with respect to his last position;
				//use google distance matrix api by passing to it all the active agents last points and from the result , take the one that is nearer to the pickuppoint 
				String googleMatrixAPI = "http://maps.googleapis.com/maps/api/distancematrix/json?";
				List<Agent> agents = agentPersistence.findByState(AgentState.STATUS_LOGGED_IN.getState());
				
				//FIXME only if there are no logged in agents , the logged off ones will be taken into account ????
				/*
				if(agents.isEmpty())
					agents = agentPersistence.findByState(false);
				*/
				StringBuilder originsSb  = new StringBuilder();
				for(int i=0;i<agents.size();i++)
				{
					Agent agente =agents.get(i);
					originsSb.append(agente.getLastPos_latitude()+","+agente.getLastPos_longitude()+"|");
				}
				int pipeIndex = originsSb.lastIndexOf("|");
				if(pipeIndex!=-1)
				{
					originsSb = originsSb.replace(pipeIndex, pipeIndex+1, "");
					googleMatrixAPI = HttpUtil.addParameter(googleMatrixAPI, "origins", originsSb.toString());
					googleMatrixAPI = HttpUtil.addParameter(googleMatrixAPI, "destinations", senderPickupAddress);
					googleMatrixAPI = HttpUtil.addParameter(googleMatrixAPI, "sensor", false);
				}else
					throw new SystemException("no agent exists that can take the pickup");
				try
				{
					URL url = new URL(googleMatrixAPI);
					URLConnection connection = url.openConnection();
					InputStream is = connection.getInputStream();
					if(is!=null)
					{
						BufferedReader br = new BufferedReader(new InputStreamReader(is));
						StringBuilder jsonresponseBuilder = new StringBuilder();
						String line=null;
						while((line= br.readLine())!=null)
							jsonresponseBuilder.append(line);
						
						JSONObject object = JSONFactoryUtil.createJSONObject(jsonresponseBuilder.toString());
						if(!object.getString("status").equals("OK"))
						{
							throw new SystemException("unable to calculate estimated time");
						}
						JSONArray rows = object.getJSONArray("rows");
						int bestAgentIndex=-1;// this will contain the index corresponding to the nearest agent
						long bestTravelDurationFromAgent=0;
						for(int i=0;i<rows.length();i++)
						{
							JSONObject row = rows.getJSONObject(i);
							if(row !=null )
							{
								JSONArray elements =  row.getJSONArray("elements");
								/*take just the first element , because we are not asking for a matriz but just for a vector*/
								JSONObject element = elements.getJSONObject(0);
								if(element.getString("status").equalsIgnoreCase("OK"))
								{
									long duration = element.getJSONObject("duration").getLong("value");
									if(duration >bestTravelDurationFromAgent)
									{
										bestTravelDurationFromAgent=duration;
										bestAgentIndex = i;
									}
								}
							}
						}
						if(bestAgentIndex==-1)
						{
							throw new SystemException();
						}
						/*set the agent which corresponds to this index, the nearest one in terms of duration*/
						agentId = agents.get(bestAgentIndex).getAgentId();
						agent = agentLocalService.getAgent(agentId);
					}
				}catch(Exception e)
				{
					throw new SystemException();
				}

			
			/*
			 * If the agentId found comes form the Matrix direction service , 
			 * than we do not need to let google calculate also the distance in terms of duration
			 * from the agent point to the delivery address :
			 * We just need to calculate the distance between the delivery address and the pickup address
			 */
				Delivery lastPendingDelivery = deliveryFinder.findLastPendingDeliveryForAgent(agentId);
				long referencelastTimeStamp=0;
				String lastAddressToVisit = "";
				
				if(lastPendingDelivery==null)
				{
					lastAddressToVisit= String.valueOf(agent.getLastPos_latitude()).replaceAll(",", ".")+","+String.valueOf(agent.getLastPos_longitude()).replaceAll(",", ".");
					referencelastTimeStamp = System.currentTimeMillis();
				}else
				{
					/*the delivery is always after the pickup*/
					lastAddressToVisit = lastPendingDelivery.getRecipientAddress();
					referencelastTimeStamp = lastPendingDelivery.getEstimatedDeliveryTime();
				}
				/*
				if(lastPendingDelivery.getEstimatedDeliveryTime()>lastPendingDelivery.getEstimatedPickupTime())
				{
					
					lastAddressToVisit = lastPendingDelivery.getRecipientAddress();
					referencelastTimeStamp = lastPendingDelivery.getEstimatedDeliveryTime();
				}else
				{
					lastAddressToVisit = lastPendingDelivery.getSenderAddress();
					referencelastTimeStamp = lastPendingDelivery.getEstimatedPickupTime();
				}
				*/
				String googleServiceUrl = "http://maps.googleapis.com/maps/api/directions/json?";
				googleServiceUrl = HttpUtil.addParameter(googleServiceUrl, "origin", lastAddressToVisit);
				googleServiceUrl = HttpUtil.addParameter(googleServiceUrl, "sensor", false);
				googleServiceUrl = HttpUtil.addParameter(googleServiceUrl, "mode", "biclying");
				StringBuilder waypointsString = new StringBuilder().append(senderPickupAddress).append("|");
				googleServiceUrl = HttpUtil.addParameter(googleServiceUrl, "waypoints", waypointsString.toString());
				googleServiceUrl = HttpUtil.addParameter(googleServiceUrl, "destination", recipientDeliveryAddress);
				InputStream is=null;
				try {
					URL url = new URL(googleServiceUrl);
					HttpURLConnection connection = (HttpURLConnection)url.openConnection();
					is = connection.getInputStream();
					if(is!=null)
					{
						BufferedReader br = new BufferedReader(new InputStreamReader(is));
						StringBuilder jsonresponseBuilder = new StringBuilder();
						String line=null;
						while((line= br.readLine())!=null)
							jsonresponseBuilder.append(line);
						
						JSONObject object = JSONFactoryUtil.createJSONObject(jsonresponseBuilder.toString());
						if(!object.getString("status").equals("OK"))
						{
							throw new SystemException("unable to calculate estimated time");
						}
						JSONArray routes = object.getJSONArray("routes");
						if(routes.length()==1)
						{
							JSONArray legs = routes.getJSONObject(0).getJSONArray("legs");
							int receivedLegs = legs.length();
							if(receivedLegs!=2)
							{
								throw new SystemException();
							}
							int durationToPickup = legs.getJSONObject(0).getJSONObject("duration").getInt("value");
							int durationToDelivery = legs.getJSONObject(1).getJSONObject("duration").getInt("value");
							
							Calendar cal = GregorianCalendar.getInstance();
							
							cal.setTimeInMillis(referencelastTimeStamp);
							cal.add(Calendar.SECOND, durationToPickup);
							del.setEstimatedPickupTime(cal.getTimeInMillis());
							
							cal.add(Calendar.SECOND, durationToDelivery);
							del.setEstimatedDeliveryTime(cal.getTimeInMillis());
							
						}else
							throw new SystemException();
					}
					//String encodedUrl = HttpUtil.encodeURL(parameters.toString());
				} catch (MalformedURLException e) {
					e.printStackTrace();
				}
				catch (IOException e) {
					e.printStackTrace();
				} catch (JSONException e) {
					e.printStackTrace();
				}finally
				{
					try {
						if(is!=null)
							is.close();
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			
			
			/*
			 * TODO calculate the estimated pickup time.
			 * This value is based on the enqueued deliveries the pony has got. The new Parcel will be given the uppest time + the time required to go from that time to the new pickup destination;
			 * This 
			 * 
			 * */
			del.setAgentId(agentId);
			del.setSubmissionTimestamp(System.currentTimeMillis());
			del = deliveryPersistence.update(del, false);
			
		return del;
		
		/*
		 * Call the service responsible of creating a new Delivery , even with email addresses
		 * */
	}
	private boolean checkAddress(String address) {
		try {
			if(address==null)
				return false;
			String googleGeocodingApiUrl ="http://maps.googleapis.com/maps/api/geocode/json?";
			googleGeocodingApiUrl = HttpUtil.addParameter(googleGeocodingApiUrl, "address", address);
			googleGeocodingApiUrl = HttpUtil.addParameter(googleGeocodingApiUrl, "sensor", false);
			URL url = new URL(googleGeocodingApiUrl);
			HttpURLConnection connection = (HttpURLConnection)url.openConnection();
			InputStream is = connection.getInputStream();
			if(is!=null)
			{
				BufferedReader br = new BufferedReader(new InputStreamReader(is));
				StringBuilder sb = new StringBuilder();
				String line;
				while((line = br.readLine())!=null)
					sb.append(line);
				JSONObject object = JSONFactoryUtil.createJSONObject(sb.toString());
				String status = object.getString("status");
				if(status.equalsIgnoreCase("OK"))
				{
					return true;
				}
			}
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (JSONException e) {
			e.printStackTrace();
		}
		return false;
	}
}